// Copyright 2011, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.common.lib.conf;

import com.google.common.annotations.VisibleForTesting;
import com.google.inject.AbstractModule;
import com.google.inject.Provider;
import com.google.inject.name.Names;

import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.CombinedConfiguration;
import org.apache.commons.configuration.Configuration;

import java.net.URL;
import java.util.Iterator;
import java.util.List;

/**
 * Module for configuration.
 *
 * @author Adam Rogal
 */
public abstract class ConfigurationModule extends AbstractModule {

  private final String BUILD_PROPERTIES = "props/google-ads-api-java-build.properties";
  private final ConfigurationHelper configurationHelper;

  /**
   * Constructor.
   */
  public ConfigurationModule() {
    configurationHelper = new ConfigurationHelper();
  }

  /**
   * Constructor.
   * @param configurationHelper the configuration helper.
   */
  @VisibleForTesting
  ConfigurationModule(ConfigurationHelper configurationHelper) {
    this.configurationHelper = configurationHelper;
  }

  @Override
  protected void configure() {}

  /**
   * Configures configurations.
   *
   * @param apiConfigurationClass the API configuration class
   * @param apiProperties the API properties file
   * @param adsLibConfigurationClass the lib configuration class
   * @param libProperties the lib properties files
   * @param adsBuildConfigurationClass the build configuration class
   * @param buildProperties the build properties files
   */
  protected void configureConfigurations(
      Class<? extends AdsApiConfiguration> apiConfigurationClass,
      List<URL> apiProperties,
      Class<? extends AdsLibConfiguration> adsLibConfigurationClass,
      List<URL> libProperties,
      Class<? extends AdsBuildConfiguration> adsBuildConfigurationClass,
      List<URL> buildProperties) {
    bind(AdsApiConfiguration.class).to(apiConfigurationClass).asEagerSingleton();
    bind(AdsLibConfiguration.class).to(adsLibConfigurationClass).asEagerSingleton();
    bind(AdsBuildConfiguration.class).to(adsBuildConfigurationClass).asEagerSingleton();
    bind(Configuration.class).annotatedWith(Names.named("api")).toProvider(
        new ConfigurationProvider(apiProperties));
    bind(Configuration.class).annotatedWith(Names.named("lib")).toProvider(
        new ConfigurationProvider(libProperties));

    buildProperties.add(ConfigurationModule.class.getResource(BUILD_PROPERTIES));
    bind(Configuration.class).annotatedWith(Names.named("build")).toProvider(
        new ConfigurationProvider(buildProperties));
  }

  /**
   * Provider for {@link Configuration}.
   */
  public class ConfigurationProvider implements Provider<Configuration> {

    private final List<URL> propertyUrls;

    /**
     * Constructor.
     *
     * @param propertyUrls the file names of the properties.
     */
    public ConfigurationProvider(List<URL> propertyUrls) {
      this.propertyUrls = propertyUrls;
    }

    public Configuration get() {
      CombinedConfiguration combinedConfiguration = new CombinedConfiguration();
      for (URL propertyUrl : propertyUrls) {
        if (propertyUrl != null) {
          final Configuration configuration = configurationHelper.fromFile(propertyUrl);
          if (configuration instanceof AbstractConfiguration) {
            combinedConfiguration.addConfiguration((AbstractConfiguration) configuration);
          } else {
            combinedConfiguration.addConfiguration(new AbstractConfiguration() {

              public boolean isEmpty() {
                return configuration.isEmpty();
              }

              public Object getProperty(String key) {
                return configuration.getProperty(key);
              }

              @SuppressWarnings("rawtypes") // No rawtype in class.
              public Iterator getKeys() {
                return configuration.getKeys();
              }

              public boolean containsKey(String key) {
                return configuration.containsKey(key);
              }

              @Override
              protected void addPropertyDirect(String key, Object value) {
                configuration.addProperty(key, value);
              }
            });
          }
        }
      }
      return combinedConfiguration;
    }
  }
}
